#include <string>
#include "exportDialog.h"
#include "dataStructures.h"
#define SETTEXTBOX(hwnd,id,str) { HWND hTB = GetDlgItem(hwnd,id);if(hTB)SendMessage(hTB, WM_SETTEXT, 0, (LPARAM)str);}

using namespace std;

//#define READABLE 

// Linked list of vertex normals

class VNormal {

public:

	Point3 norm;

	DWORD smooth;

	VNormal *next;

	BOOL init;

	VNormal() {smooth=0;next=NULL;init=FALSE;norm=Point3(0,0,0);}

	VNormal(Point3 &n,DWORD s) {next=NULL;init=TRUE;norm=n;smooth=s;}

	~VNormal() {delete next;}

	void AddNormal(Point3 &n,DWORD s){

		if (!(s&smooth) && init) {

			if (next) next->AddNormal(n,s);

			else {

				next = new VNormal(n,s);

			}

		} 

		else {

			norm += n;

			smooth |= s;

			init = TRUE;

		}

	}


	Point3 &GetNormal(DWORD s){

		if (smooth&s || !next) return norm;

		else return next->GetNormal(s); 

	}


	void Normalize(){

		VNormal *ptr = next, *prev = this;

		while (ptr) {

			if (ptr->smooth&smooth) {

				norm += ptr->norm;

				prev->next = ptr->next;

				delete ptr;

				ptr = prev->next;

			} 

			else {

				prev = ptr;

				ptr = ptr->next;

			}

		}

		norm = ::Normalize(norm);

		if (next) next->Normalize();

	}


};

Point3* ComputeVertexNormals(Mesh *mesh) {

	Face *face; 

	Point3 *verts;

	Point3 v0, v1, v2;


	//Allocate vert normals
	Point3 *vNorms = new Point3 [mesh->numVerts];
	for(int i = 0; i < mesh->numVerts;i++)
		vNorms[i] = Point3(0,0,0);

	verts = mesh->verts; 
	face = mesh->faces; 

	//Calculate face normals
	Point3 *fNorms = new Point3 [mesh->numFaces];
	for(int i = 0; i < mesh->numFaces;i++)
	{
		Point3 v0 = verts[face[i].v[0]];
		Point3 v1 = verts[face[i].v[1]];
		Point3 v2 = verts[face[i].v[2]];

		fNorms[i] = (v1-v0)^(v2-v1);
		//fNorms[i] = fNorms[i].Normalize();
	}

	//Calculate vertex normals
	for(int i = 0; i < mesh->numVerts;i++)
	{
		Point3 *currentVert = &verts[i];

		int shareCount = 0;
		Point3 sumedNormal = Point3(0,0,0);

		for(int j = 0; j < mesh->numFaces;j++)
		{
			if(i == face[j].v[0] || 
			   i == face[j].v[1] || 
			   i == face[j].v[2])
			{
				shareCount ++;
				sumedNormal += fNorms[j];
			}
		}

		if(shareCount>0)
		{
			sumedNormal.x /= (float)shareCount;
			sumedNormal.y /= (float)shareCount;
			sumedNormal.z /= (float)shareCount;

			vNorms[i] = sumedNormal.Normalize();
		}
		else
		{
			vNorms[i] = Point3(0,0,0);
		}
	}
	return vNorms;
}


HWND hConsole = NULL;
Interface *g_pIp = NULL;
void writeToConsole(string str)
{
	if(!hConsole)
		return;

	char buffer[256];
	ZeroMemory(buffer,sizeof(buffer));
	SendMessage(hConsole, WM_GETTEXT, 256, (LPARAM)buffer);

	string s = string(buffer) + string("\n") + str;
	SendMessage(hConsole, WM_SETTEXT, 0, (LPARAM)s.c_str());
}

void processNodeStats(INode* pNode, int* pCounts)
{
	if(pNode && pCounts)
		for (int i=0,n=pNode->NumberOfChildren(); i<n; i++)
		{
			INode *pChild = pNode->GetChildNode(i);
			if(!pChild) 
				continue;

			Object *pObject = pChild->EvalWorldState(EXPTIME).obj;
			if(!pObject) 
				continue;

			switch(pObject->SuperClassID()){
case GEOMOBJECT_CLASS_ID:
	pCounts[0]++;
	break;
case LIGHT_CLASS_ID:
	pCounts[1]++;
	break;
case HELPER_CLASS_ID:
	pCounts[2]++;
	break;
			}

			for (int j=0,m=pChild->NumberOfChildren(); j<m; j++)
				processNodeStats(pChild->GetChildNode(j),pCounts);
		}
}

void applyDialgoStats(HWND hWnd)
{
	SETTEXTBOX(hWnd,IDC_STATIC_NUMFACES,"-");
	SETTEXTBOX(hWnd,IDC_STATIC_NUMLIGHTS,"-");
	SETTEXTBOX(hWnd,IDC_STATIC_NUMENTITIES,"-");

	if(!g_pIp)
		return;

	int c[3] = {0,0,0};
	processNodeStats(g_pIp->GetRootNode(), c);

	char buffer[16]; 
	ZeroMemory(buffer, sizeof(buffer));

	itoa(c[0],buffer,10);
	SETTEXTBOX(hWnd,IDC_STATIC_NUMFACES,buffer);
	itoa(c[1],buffer,10);
	SETTEXTBOX(hWnd,IDC_STATIC_NUMLIGHTS,buffer);
	itoa(c[2],buffer,10);
	SETTEXTBOX(hWnd,IDC_STATIC_NUMENTITIES,buffer);

}

string getEditbox(HWND hWnd, INT ID)
{
	HWND hComponent = GetDlgItem(hWnd,ID);
	if(!hComponent)
		return string("-");

	CHAR buffer[256];
	ZeroMemory(buffer,sizeof(buffer));

	SendMessage(hComponent, WM_GETTEXT, 0, (LPARAM)buffer);
	return string(buffer);
}

float getFloatEditbox(HWND hWnd, INT ID)
{
	return atof(getEditbox(hWnd,ID).c_str());
}

exportPacket getDialogData(HWND hWnd)
{
	exportPacket exportData;
	/*
	//TODO:
	exportData.env
	exportData.weather
	*/

	//Filename
	exportData.filename = getEditbox(hWnd,IDC_EXPFILENAME);

	//World title/description
	getEditbox(hWnd,IDC_EXPWORLDNAME).copy(exportData.global.worldName,
		sizeof(exportData.global.worldName)/sizeof(char));
	getEditbox(hWnd,IDC_EXPWORLDDESC).copy(exportData.global.worldDesc,
		sizeof(exportData.global.worldDesc)/sizeof(char));

	//Fog
	exportData.fog.density = getFloatEditbox(hWnd,IDC_EXPFOGDEN);
	exportData.fog.color[0] = getFloatEditbox(hWnd,IDC_EXPFOGRED);
	exportData.fog.color[1] = getFloatEditbox(hWnd,IDC_EXPFOGGREEN);
	exportData.fog.color[2] = getFloatEditbox(hWnd,IDC_EXPFOGBLUE);

	return exportData;

}
INT_PTR CALLBACK ExportDlgProc(HWND hWnd, UINT msg, 
							   WPARAM wParam, LPARAM lParam)
{
	switch (msg) 
	{
	case WM_INITDIALOG:
		{	
			CenterWindow(hWnd, GetParent(hWnd)); 

			CheckDlgButton(hWnd, IDC_EXPCOLLISIONDATA, TRUE);
			CheckDlgButton(hWnd, IDC_EXPENTITIES, TRUE); 
			CheckDlgButton(hWnd, IDC_EXPLIGHTS, TRUE);

			hConsole = GetDlgItem(hWnd, IDC_INFO);

			SETTEXTBOX(hWnd, IDC_EXPFILENAME, STR_DEFAULTFILE);

			SETTEXTBOX(hWnd, IDC_EXPWORLDNAME, STR_DEFAULTWORLDNAME);
			SETTEXTBOX(hWnd, IDC_EXPWORLDDESC, STR_DEFAULTWORLDDESC);

			SETTEXTBOX(hWnd, IDC_EXPFOGDEN, "1.0");
			SETTEXTBOX(hWnd, IDC_EXPFOGRED, "1.0");
			SETTEXTBOX(hWnd, IDC_EXPFOGGREEN, "1.0");
			SETTEXTBOX(hWnd, IDC_EXPFOGBLUE, "1.0");

			applyDialgoStats(hWnd);
			SETTEXTBOX(hWnd, IDC_INFO, "Ready...");
		}
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam)) 
		{
		case IDEXPORT:
			{
				WorldExp exporter;

				exporter.data.fog.color[0] = 1.0f;
				exporter.data.fog.color[1] = 1.0f;
				exporter.data.fog.color[2] = 1.0f;
				exporter.data.fog.density = 1.0f;

				string(STR_DEFAULTWORLDDESC).copy(exporter.data.global.worldDesc,256,sizeof(char));
				string(STR_DEFAULTWORLDNAME).copy(exporter.data.global.worldName,64,sizeof(char));

				string("sky_1.JPG").copy(exporter.data.env.backgroundFilename,64,sizeof(char));
				ZeroMemory(exporter.data.env.glareFilenames, sizeof(exporter.data.env.glareFilenames));
				exporter.data.env.sunAngle = 0.0f;
				exporter.data.env.sunHeight = 0.0f;



				exporter.data = getDialogData(hWnd);
				exporter.data.filename = STR_DEFAULTFILE;
				exporter.doExport();
			}break;

		case IDCANCEL:
			EndDialog(hWnd, 1);
			break;

		default:
			return FALSE;
		}
	}
	return TRUE;
}       


INT_PTR CALLBACK ModelDlgProc(HWND hWnd, UINT msg, 
							  WPARAM wParam, LPARAM lParam)
{
	switch (msg) 
	{
	case WM_INITDIALOG:
		{	
			return TRUE;
		}

	case WM_COMMAND:{
		switch (LOWORD(wParam)) 
		{
		case ID_WRITEMODEL:
			{
				HWND editBox = GetDlgItem(hWnd, IDC_MODFILENAME);
				HWND frameCountEditBox = GetDlgItem(hWnd, IDC_EXPFRAMECOUNT);
				INT frameCount = 1;
				string filename(STR_DEFAULTMODELFILE);
				if(editBox)
				{
					int numChar = SendMessage(editBox, WM_GETTEXTLENGTH, 0,0) + 1;

					char tmpBuff[64];
					ZeroMemory(tmpBuff,sizeof(tmpBuff));
					SendMessage(editBox, WM_GETTEXT, numChar, (LPARAM)tmpBuff);
					filename+=tmpBuff;
					filename+=".mod";

					ZeroMemory(tmpBuff,sizeof(tmpBuff));
					SendMessage(frameCountEditBox, WM_GETTEXT, 4, (LPARAM)tmpBuff);
					frameCount = atoi(tmpBuff);
					if(frameCount<1)
						frameCount=1;
				}
				else
					filename += "unnamed.mod";

				ModelExp modelWrite;
				modelWrite.doExport(filename,frameCount);
			}
		case IDCANCEL:
			{
				EndDialog(hWnd,0);break;
			}
		}
					}
	}
	return TRUE;
}

VOID showExportDialog(){
	DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_EXPORT), 0, ExportDlgProc, 0);
}
VOID showModelDialog(){
	DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MODELDLG), 0, ModelDlgProc, 0);
}


BOOL MtlKeeper::AddMtl(Mtl* mtl)
{
	if (!mtl) {
		return FALSE;
	}

	int numMtls = mtlTab.Count();
	for (int i=0; i<numMtls; i++) {
		if (mtlTab[i] == mtl) {
			return FALSE;
		}
	}
	mtlTab.Append(1, &mtl, 25);

	return TRUE;
}

int MtlKeeper::GetMtlID(Mtl* mtl)
{
	int numMtls = mtlTab.Count();
	for (int i=0; i<numMtls; i++) {
		if (mtlTab[i] == mtl) {
			return i;
		}
	}
	return -1;
}

int MtlKeeper::Count()
{
	return mtlTab.Count();
}

Mtl* MtlKeeper::GetMtl(int id)
{
	return mtlTab[id];
}




WorldExp::WorldExp()
{
	isWorldProcessDone= false;

	fogSize	= 0;
	sunSize	= 0;
	weatherSize	= 0;
	entitySize	= 0;
	meshSize	= 0;

	pWorldMesh = NULL;

}

WorldExp::~WorldExp()
{
}

void WorldExp::doExport() 
{
	try{
		if(g_pIp == NULL)
			throw Exception("Internal Error: No Interface Pointer...");

		preprocess(g_pIp->GetRootNode());	
		if(pWorldMesh == NULL)
			throw Exception("Internal Error: No \"World Mesh\" found...");

		writeToConsole("Scene preprocessing complete...");

		pFilePtr = fopen(data.filename.c_str(),"wb");
		//pFilePtr = _tfopen(data.filename.c_str(),_T("wb"));
		if (!pFilePtr)
			throw Exception("Internal Error: Could not create new file...");

		writeToConsole("Export data file created...");

		writeHeader();
		writeGlobalInfo();
		writeFog();
		writeEnvironment();

		writeToConsole("Scene data written...");

		writeShaders();

		writeToConsole("Shaders written...");

		writeWorldMesh();

		writeToConsole("Mesh data written...");

		writeEntities();

		writeToConsole("Entity data written...");

		writeToConsole("Complete");

		fclose(pFilePtr);
	}
	catch(WorldExp::Exception e)
	{
		MessageBoxA(0,e.msg.c_str(),0,0);
	}
}

int WorldExp::getNodeType(INode *pNode)
{
	if(pNode==NULL)
		return -1;

	ObjectState objState = pNode->EvalWorldState(EXPTIME);
	if(!objState.obj)
		return -1;

	return objState.obj->SuperClassID();
}

void WorldExp::processNode(INode* node) 
{
	try{
		if(node == NULL)
			throw Exception(NULL);

		ObjectState os = node->EvalWorldState(EXPTIME);
		if (!os.obj)
			throw Exception(NULL);

		switch(os.obj->SuperClassID()) 
		{
		case LIGHT_CLASS_ID:
			writeLightEntity(node); 
			break;
		case HELPER_CLASS_ID:	
			writeGenricEntity(node); 
			break;
		}

		for (int c = 0; c < node->NumberOfChildren(); c++)
			processNode(node->GetChildNode(c));

	}
	catch(WorldExp::Exception)
	{	}
}

void WorldExp::preprocess(INode *pNode)
{
	try{

		if(pNode == NULL)
			return;		//throw Exception("No node to search from");

		for (int i=0,n=pNode->NumberOfChildren(); i<n; i++)
		{
			INode* pCurrentNode = pNode->GetChildNode(i);
			if(getNodeType(pCurrentNode) != GEOMOBJECT_CLASS_ID) 
				continue;

			if(string(pCurrentNode->GetName()) == "World")
			{
				if(pWorldMesh)
					throw Exception("Duplicated world meshs found!");
				else
					pWorldMesh = pCurrentNode;
				break;
			}

			preprocess(pCurrentNode);
		}

		aquireMaterials();

	}
	catch(WorldExp::Exception e)
	{
		writeToConsole(e.msg);
	}
}

void WorldExp::aquireMaterials()
{
	try{
		if(!pWorldMesh)
			throw Exception("No world mesh ptr");

		Mtl *pMtl = pWorldMesh->GetMtl();

		if(!pMtl)
			throw Exception("No material ptr");

		if(pMtl->ClassID() == Class_ID(MULTI_CLASS_ID, 0x00))
		{
			MultiMtl* pMultiMat = (MultiMtl*)pMtl;
			for(int i = 0; i < pMultiMat->NumSubMtls(); i++)
				mtlList.AddMtl(pMultiMat->GetSubMtl(i));

		}
		else if(pMtl->ClassID() == Class_ID(DMTL_CLASS_ID, 0x00))
		{
			StdMat* pStndMat = (StdMat*)pMtl;
			mtlList.AddMtl(pStndMat->GetSubMtl(0));
		}
		else
			throw Exception("Unsupported material type");
	}
	catch(WorldExp::Exception e)
	{
		writeToConsole(e.msg);
	}
}

void WorldExp::writeHeader()
{
	if(!pFilePtr){
		writeToConsole("Header write failed!");
		return;
	}

	MapHeader headerData;
	headerData.version = VERSION;
	headerData.hasLightMap = 0;

			BOOL needDel;
		TriObject* tri = GetTriObjectFromNode(pWorldMesh, EXPTIME);
		if (tri)
		{

		Mesh* mesh = &tri->GetMesh();
		if (mesh)
		{
			if(mesh->getNumMaps() > 2)
				headerData.hasLightMap = true;
		}
		}

	fwrite(&headerData, sizeof(MapHeader), 1, pFilePtr);
	writeToConsole("Header written...");
}
void WorldExp::writeGlobalInfo()
{
	if(!pFilePtr){
		writeToConsole("Global info write failed!");
		return;
	}

	MapGlobalInfo infoData;
	ZeroMemory(&infoData,sizeof(MapGlobalInfo));

	strncpy(infoData.worldName, data.global.worldName, sizeof(char)*64);
	strncpy(infoData.worldDesc, data.global.worldDesc, sizeof(char)*256);

	infoData.MapFogInfoSize	= fogSize;
	infoData.worldSunSize	= sunSize;
	infoData.worldWeatherSize	= weatherSize;
	infoData.worldEntitySize	= entitySize;
	infoData.worldMeshSize	= meshSize;

	fwrite(&infoData, sizeof(MapGlobalInfo), 1, pFilePtr);
	writeToConsole("GlobalInfo written...");
}

void WorldExp::writeFog()
{
	if(!pFilePtr){
		writeToConsole("fog write failed!");
		return;
	}
	data.fog.color[0] = 
		data.fog.color[1] = 
		data.fog.color[2] = 1.0f;
	data.fog.density=1.0f;
	fwrite(&data.fog, sizeof(MapFogInfo), 1, pFilePtr);
	writeToConsole("Fog written...");
}
void WorldExp::writeEnvironment()
{
	if(!pFilePtr){
		writeToConsole("Environment write failed!");
		return;
	}

	data.env.sunHeight =
		data.env.sunAngle = 2.0f;
	ZeroMemory(data.env.backgroundFilename,sizeof(data.env.backgroundFilename));
	ZeroMemory(data.env.glareFilenames,sizeof(data.env.glareFilenames));


	fwrite(&data.env, sizeof(MapEnvInfo), 1, pFilePtr);
	writeToConsole("Environment written...");
}


bool isNameField(string& name)
{
	if(name.length() > 5)
	{
		return
		  ( name[0] == 'f' &&
			name[1] == 'i' &&
			name[2] == 'e' &&
			name[3] == 'l' &&
			name[4] == 'd');
	}
	return false;
}

void WorldExp::writeEntities()
{
#ifdef READABLE
	fprintf(pFilePtr, "WRITE MESH ENTITIES - START\n");
	fprintf(pFilePtr, "WRITE GENERIC ENTITIES - START\n");
	fprintf(pFilePtr, "NUMBER OF GENERIC ENTITIES:\n");
#endif

	INode *pRoot = g_pIp->GetRootNode();

	int entityCount = 0;
	for (int i=0,n=pRoot->NumberOfChildren(); i<n; i++)
		if(getNodeType(pRoot->GetChildNode(i)) == GEOMOBJECT_CLASS_ID && pRoot != pWorldMesh)
			entityCount++;

#ifdef READABLE
	fprintf(pFilePtr,"%f",entityCount);
#else
	fwrite(&entityCount, sizeof(int), 1, pFilePtr);
#endif

	//write fields first
	for (int i=0,n=pRoot->NumberOfChildren(); i<n; i++)
	{
		INode *pNode = pRoot->GetChildNode(i);
		if(pWorldMesh == pNode)
			continue;

		int nodeTypeID = getNodeType(pNode);
		if(nodeTypeID == HELPER_CLASS_ID || nodeTypeID == GEOMOBJECT_CLASS_ID)
		{
			
			if(isNameField(string(pNode->GetName())))
				writeGenricEntity(pNode);
		}
	}

	//write non fields second
	for (int i=0,n=pRoot->NumberOfChildren(); i<n; i++)
	{
		INode *pNode = pRoot->GetChildNode(i);
		if(pWorldMesh == pNode)
			continue;

		int nodeTypeID = getNodeType(pNode);
		if(nodeTypeID == HELPER_CLASS_ID || nodeTypeID == GEOMOBJECT_CLASS_ID)
		{
			
			if(!isNameField(string(pNode->GetName())))
				writeGenricEntity(pNode);
		}
	}

#ifdef READABLE
	fprintf(pFilePtr, "WRITE GENERIC ENTITIES - END\n");
	fprintf(pFilePtr, "WRITE LIGHT ENTITIES - START\n");
	fprintf(pFilePtr, "NUMBER OF LIGHTS:\n");
#endif

	int lightCount = 0;
	for (int i=0,n=pRoot->NumberOfChildren(); i<n; i++)
		if(getNodeType(pRoot->GetChildNode(i)) == LIGHT_CLASS_ID)
			lightCount++;

#ifdef READABLE
	fprintf(pFilePtr,"%f",lightCount);
#else
	fwrite(&lightCount, sizeof(int), 1, pFilePtr);
#endif

	for (int i=0,n=pRoot->NumberOfChildren(); i<n; i++)
	{
		INode *pNode = pRoot->GetChildNode(i);
		if(pWorldMesh == pNode)
			continue;

		if(getNodeType(pNode) == LIGHT_CLASS_ID)
			writeLightEntity(pNode);
	}

#ifdef READABLE
	fprintf(pFilePtr, "WRITE LIGHT ENTITIES - END\n");
	fprintf(pFilePtr, "WRITE MESH ENTITIES - END\n");
#endif
}

void WorldExp::writeWorldMesh()
{
	if(pWorldMesh == NULL)
		return;
	try{

		ObjectState os = pWorldMesh->EvalWorldState(0);
		if (!os.obj)
			throw Exception("Invalid world mesh state at 0th frame");

		for(int i = 0, n = mtlList.Count();i<n;i++)
			writeMeshGroup(pWorldMesh,i);
	}
	catch(WorldExp::Exception e)
	{
		writeToConsole(e.msg);
	}
}

void WorldExp::writeLightEntity(INode* node)
{
	try{
		if(!node)
			throw Exception("Internal error: node == null");

		worldLightEntity exportData;
		Point3 entityPosition = node->GetNodeTM(0).GetTrans();

		ObjectState os = node->EvalWorldState(0);
		if (!os.obj)
			throw Exception("No light entity data at 0th frame");

#ifdef READABLE
		fprintf(pFilePtr, "WRITE LIGHT - START\n");
#endif

		GenLight* light = (GenLight*)os.obj;
		struct LightState lightData;
		light->EvalLightState(0, FOREVER, &lightData);

		exportData.color[0] = lightData.color.r;
		exportData.color[1] = lightData.color.g;
		exportData.color[2] = lightData.color.b;
		exportData.position[0] = entityPosition.x;
		exportData.position[1] = entityPosition.y;
		exportData.position[2] = entityPosition.z;
		exportData.range = lightData.intens;


#ifdef READABLE
		fprintf(pFilePtr,"Position: (%f,%f,%f) Color: (%f,%f,%f) Intensity: %f\n", exportData.position[0],exportData.		position[1],exportData.position[2],exportData.color[0],exportData.color[1],exportData.color[2],exportData.			range);
		fprintf(pFilePtr, "WRITE LIGHT - END\n");
#else
		fwrite(&exportData, sizeof(worldLightEntity), 1, pFilePtr);
#endif

	}
	catch(WorldExp::Exception e)
	{
		writeToConsole(e.msg);
	}

}
string toString(int value)
{
	CHAR buffer[10];
	ZeroMemory(buffer, sizeof(buffer));
	
	itoa(value,buffer,10);
	return string(buffer);
}
void WorldExp::writeGenricEntity(INode* node)
{
	try{
		if(!node)
			throw Exception("Internal error: node == null");

#ifdef READABLE
		fprintf(pFilePtr, "WRITE ENTITY - START\n");
		fprintf(pFilePtr, "NAME: %s\n", node->GetName());
#endif

		worldEntity exportData;
		string tmp(node->GetName());

		int i = 0;
		for(i = 0; i < tmp.length(); i++)
		{
			char c = tmp[i];

			if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')))
				break;
		}


		string exportName = tmp.substr(0,i);

		exportName.copy(exportData.name, sizeof(exportData.name) / sizeof(char));

		Point3 entityPosition = node->GetObjectTM(0).GetTrans();

		float yaw,b,c;
		node->GetNodeTM(0).GetYawPitchRoll( &yaw,&b,&c);
#define DEGTORAD(x) ((x)*PI/180.0F)
#define RADTODEG(x) ((x)*180.0F/PI)

		c*=-1;
//		r = yaw
//			p = pic
//			y = rol
		//string s = string( "Y: " + toString( RADTODEG(yaw) )+" P: " + toString( RADTODEG(b) ) + " R: " + toString( RADTODEG(c) ));
		//MessageBox(0,s.c_str(),exportData.name,0);

		//b -= (3.14159 / 2.0f);
		Point3 r0 = Point3(cos(c),0.0f,-sin(c));
		Point3 r1 = Point3(0,1,0);//transform.GetRow(1);
		Point3 r2 = Point3(sin(c),0.0f,cos(c));//transform.GetRow(2);
		Point3 r3 = entityPosition;
		
		exportData.transform[0][0] = r0.x;	exportData.transform[0][1] = r0.y; exportData.transform[0][2] = r0.z; exportData.transform[0][3] = 0;
		exportData.transform[1][0] = r1.x;	exportData.transform[1][1] = r1.y; exportData.transform[1][2] = r1.z; exportData.transform[1][3] = 0;
		exportData.transform[2][0] = r2.x;	exportData.transform[2][1] = r2.y; exportData.transform[2][2] = r2.z; exportData.transform[2][3] = 0;
		exportData.transform[3][0] = r3.x;	exportData.transform[3][1] = r3.z; exportData.transform[3][2] = r3.y; exportData.transform[3][3] = 1;

#ifdef READABLE
		fprintf(pFilePtr,"Position: (%f,%f,%f) Angle: %f\n", entityPosition.x,entityPosition.y,entityPosition.z,entityAngle);
#else 
		fwrite(&exportData, sizeof(worldEntity), 1, pFilePtr);
#endif

#ifdef READABLE
		fprintf(pFilePtr, "WRITE ENTITY - END\n");
#endif
	}
	catch(WorldExp::Exception e)
	{
		writeToConsole(e.msg);
	}
}

void WorldExp::writeShaders()
{
	const int numShaders = mtlList.Count();

#ifdef READABLE
	fprintf(pFilePtr, "---------------- WRITE SHADERS START ----------------\n");
	fprintf(pFilePtr, "NUMSHADERS: %i\n",numShaders);
#else
	fwrite(&numShaders, sizeof(int),1,pFilePtr);
#endif

	for (int i=0; i<numShaders; i++)
		writeShader(mtlList.GetMtl(i));

#ifdef READABLE
	fprintf(pFilePtr, "---------------- WRITE SHADERS END ----------------\n");
#endif
}


void WorldExp::writeShader(Mtl* mtl)
{
	try {
		if(mtl->ClassID() != Class_ID(DMTL_CLASS_ID, 0))
			throw Exception("material not 'standard'");

		if(mtl->NumSubTexmaps() <= 0)
			throw Exception("no subtextures in material");

#ifdef READABLE
		fprintf(pFilePtr, "SHADER:\n");
#endif

		char buffer[64];

		for (int i=0; i<mtl->NumSubTexmaps(); i++) 
		{
			Texmap* subTex = mtl->GetSubTexmap(i);
			ZeroMemory(buffer,sizeof(buffer));

			//Diffuse Map || Bump Map
			if(i == 1 || i == 8)
			{
				//Only supporting bitmaps
				if (subTex && subTex->ClassID() == Class_ID(BMTEX_CLASS_ID, 0x00))
				{
					string mapName(((BitmapTex *)subTex)->GetMapName());
					int lastBakSlsh = mapName.find_last_of('\\');
					if(lastBakSlsh>0)
						mapName = mapName.substr(lastBakSlsh+1,mapName.length() - lastBakSlsh);

					mapName.copy(buffer, 64);
				}
				else
				{
					string("nofile").copy(buffer, 64);
				}

#ifdef READABLE
				fprintf(pFilePtr, (i==1) ? "\tDiffuse Map: %s\n" : "\t Bump Map: %s\n", buffer);
#else
				fwrite(buffer,sizeof(char)*64,1,pFilePtr);
#endif
			}
		}

#ifdef READABLE
		fprintf(pFilePtr, "-----\n");
#endif
	}
	catch(WorldExp::Exception){
		writeToConsole(string("Warning: material") + string(mtl->GetName() + " not standard"));
	}
}

void WorldExp::writeMeshGroup(INode* node, int matID)
{
	try{
		int i;
		//Mtl* nodeMtl = node->GetMtl();
		Matrix3 tm = node->GetObjTMAfterWSM(EXPTIME);

		ObjectState os = node->EvalWorldState(EXPTIME);
		if (!os.obj || os.obj->SuperClassID()!=GEOMOBJECT_CLASS_ID) 
			throw Exception("Fatal internal error: node was mistaken for world mesh!");

		// Order of the vertices if -vely scaled
		int vx1, vx2, vx3;
		if (TMNegParity(tm)) {vx1 = 2;vx2 = 1;vx3 = 0;}else {vx1 = 0;vx2 = 1;vx3 = 2;}

		BOOL needDel;
		TriObject* tri = GetTriObjectFromNode(node, EXPTIME);
		if (!tri)
			throw Exception("Could not get triangle object from mesh node");

		Mesh* mesh = &tri->GetMesh();
		if (!mesh)
			throw Exception("Could not get mesh object from trinalge object");

		mesh->buildNormals();
		Point3* vNormals = ComputeVertexNormals(mesh);

		//This lets us know if we should bother exporting any data associated with
		//the specifeid mat ID
		int numFacesOfThisMatID = 0;
		for (i=0; i<mesh->getNumFaces(); i++)
			if(mesh->faces[i].getMatID() == matID) numFacesOfThisMatID++;

		//**----------------
		//Bomb out
		if(numFacesOfThisMatID==0)
			throw Exception("No faces match specified matID"); 

#ifdef READABLE
		fprintf(pFilePtr, "MESH GROUP (%i):\n", matID);
		fprintf(pFilePtr, "Number of Faces: %i\n", numFacesOfThisMatID);
#else
		fwrite(&numFacesOfThisMatID, sizeof(int),1,pFilePtr);
#endif

		for (i=0; i<mesh->getNumFaces(); i++) {
			if(mesh->faces[i].getMatID() != matID) continue;

			//1 == channel 1
			//i == face vert idx
			TVFace tvFace = mesh->mapFaces(1)[i];

			UVVert tcA = mesh->mapVerts(1)[(int)tvFace.t[vx1]];
			UVVert tcB = mesh->mapVerts(1)[(int)tvFace.t[vx2]];
			UVVert tcC = mesh->mapVerts(1)[(int)tvFace.t[vx3]];
			
			
			UVVert tc_lmA,tc_lmB,tc_lmC;
			Point3 vA,vB,vC;
//MessageBox(0,"F","F",0);
			if(mesh->getNumMaps() > 2)
			{
//				MessageBox(0,"!","!",0);
				if(mesh->mapFaces(2) != 0)
				{
//					MessageBox(0,"!","NOT NULL",0);
			TVFace tv_lmFace = mesh->mapFaces(2)[i];
			tc_lmA = mesh->mapVerts(2)[(int)tv_lmFace.t[vx1]];
			tc_lmB = mesh->mapVerts(2)[(int)tv_lmFace.t[vx2]];
			tc_lmC = mesh->mapVerts(2)[(int)tv_lmFace.t[vx3]];


				}
			}

			

			vA = tm * mesh->verts[mesh->faces[i].v[vx1]];
			vB = tm * mesh->verts[mesh->faces[i].v[vx2]];
			vC = tm * mesh->verts[mesh->faces[i].v[vx3]];

			Point3 nA = vNormals[mesh->faces[i].v[vx1]];
			Point3 nB = vNormals[mesh->faces[i].v[vx2]];
			Point3 nC = vNormals[mesh->faces[i].v[vx3]];
			nA.Normalize();
			nB.Normalize();
			nC.Normalize();

#ifdef READABLE
			fprintf(pFilePtr,"\nf%i: (%f %f %f) (%f %f)",i,vA.x,vA.y,vA.z,tcA.x,tcA.y);
			fprintf(pFilePtr,"\nf%i: (%f %f %f) (%f %f)",i,vB.x,vB.y,vB.z,tcB.x,tcB.y);
			fprintf(pFilePtr,"\nf%i: (%f %f %f) (%f %f)\n*",i,vC.x,vC.y,vC.z,tcC.x,tcC.y);
#else 
			vertice v;
			v.pos[0] = vA.x;	v.pos[1] = vA.y;	v.pos[2] = vA.z;
			v.tc[0] = tcA.x;	v.tc[1] = tcA.y;
			v.tc_lm[0] = tc_lmA.x;
			v.tc_lm[1] = tc_lmA.y;
			v.n[0] = nA.x;
			v.n[1] = nA.y;
			v.n[2] = nA.z;
			fwrite(&v, sizeof(vertice), 1, pFilePtr);	

			v.pos[0] = vB.x;	v.pos[1] = vB.y;	v.pos[2] = vB.z;
			v.tc[0] = tcB.x;	v.tc[1] = tcB.y;
			v.tc_lm[0] = tc_lmB.x;
		v.tc_lm[1] = tc_lmB.y;
			v.n[0] = nB.x;
			v.n[1] = nB.y;
			v.n[2] = nB.z;
			fwrite(&v, sizeof(vertice), 1, pFilePtr);	

			v.pos[0] = vC.x;	v.pos[1] = vC.y;	v.pos[2] = vC.z;
			v.tc[0] = tcC.x;	v.tc[1] = tcC.y;
			v.tc_lm[0] = tc_lmC.x;
			v.tc_lm[1] = tc_lmC.y;
			v.n[0] = nC.x;
			v.n[1] = nC.y;
			v.n[2] = nC.z;
			fwrite(&v, sizeof(vertice), 1, pFilePtr);	
#endif

		}

#ifdef READABLE
		fprintf(pFilePtr, "\n----------", matID);
#endif


	}catch(WorldExp::Exception e)
	{
		writeToConsole(e.msg);
	}
}

void WorldExp::writeCollisionModel(INode* node)
{

}
BOOL WorldExp::TMNegParity(Matrix3 &m)
{
	return (DotProd(CrossProd(m.GetRow(0),m.GetRow(1)),m.GetRow(2))<0.0)?1:0;
}

TriObject* WorldExp::GetTriObjectFromNode(INode *node, TimeValue t)
{

	Object *obj = node->EvalWorldState(t).obj;
	if (obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) { 
		TriObject *tri = (TriObject *) obj->ConvertToType(t, 
			Class_ID(TRIOBJ_CLASS_ID, 0));
		// Note that the TriObject should only be deleted
		// if the pointer to it is not equal to the object
		// pointer that called ConvertToType()

		return tri;
	}
	else {
		return NULL;
	}
}

ModelExp::ModelExp()
{

	skinName.clear();
	frames = 1;
	pFilePtr = 0;
	pMesh = 0;
}
ModelExp::~ModelExp()
{

	skinName.clear();
	pFilePtr = 0;
	pMesh = 0;
}

VOID ModelExp::doExport(INode *pNode,string filename, int numFrames)
{
try{
		frames = numFrames;

		if(!pNode)
			throw Exception("FATAL ERROR");


		ObjectState objState = pNode->EvalWorldState(EXPTIME);
		if(!objState.obj)
			throw Exception("Internal Error: Selected object has bad state...");
		else if(objState.obj->SuperClassID() != GEOMOBJECT_CLASS_ID)
			throw Exception("Error: You must selected geometry object for export...");

#ifdef READABLE
		pFilePtr = fopen(filename.c_str(),"wt");
#else
		pFilePtr = fopen(filename.c_str(),"wb");
#endif
		if (!pFilePtr)
			throw Exception("Internal Error: Could not create new file...");

		pMesh = pNode;
		getShader();
		writeHeader();
		writeMesh();

		fclose(pFilePtr);
	}
	catch(ModelExp::Exception e)
	{
		MessageBox(0,e.msg.c_str(),"Error",MB_OK);
	}
}

VOID ModelExp::doExport(string filename, int numFrames)
{
	try{
		frames = numFrames;

		
		if(!g_pIp)
			throw Exception("FATAL ERROR");

		INode *pNode = g_pIp->GetSelNode(0);
		if(!pNode)
			throw Exception("Please select an object to export...");

		doExport(pNode,filename,numFrames);

	}
	catch(ModelExp::Exception e)
	{
		MessageBox(0,e.msg.c_str(),"Error",MB_OK);
	}

}

void ModelExp::writeHeader()
{
	modelHeader header;
	ZeroMemory(header.skinName,sizeof(header.skinName));
	skinName.copy(header.skinName, 64);
	header.frames = frames;
	header.version = VERSION;

	fwrite(&header, sizeof(header), 1, pFilePtr);
}

VOID ModelExp::getShader()
{
	try{
		if(!pMesh)
			throw Exception("No pMesh!"); 

		Mtl *pMtl = pMesh->GetMtl();

		if(!pMtl)
			throw Exception("No material for pMesh!"); 

		if(pMtl->ClassID() == Class_ID(MULTI_CLASS_ID, 0x00))
		{

			MultiMtl* pMultiMat = (MultiMtl*)pMtl;
			pMtl = pMultiMat->GetSubMtl(0);
		}

		if(pMtl->ClassID() != Class_ID(DMTL_CLASS_ID, 0x00))
			throw Exception("Unsupported material type!"); 

		StdMat* pStndMat = (StdMat*)pMtl;

		Texmap* subTex = pStndMat->GetSubTexmap(1);
		if(!subTex)
			throw Exception("Diffuse subtex not found in mateiral!"); 
		else if(subTex->ClassID() != Class_ID(BMTEX_CLASS_ID, 0x00))
			throw Exception("Subtex not bitmap!"); 

		string mapName(((BitmapTex *)subTex)->GetMapName());
		int lastBakSlsh = mapName.find_last_of('\\');
		if(lastBakSlsh>0)
			mapName = mapName.substr(lastBakSlsh+1,mapName.length() - lastBakSlsh);

		skinName = mapName;

	}catch(ModelExp::Exception e)
	{
		MessageBox(0,e.msg.c_str(),"Error",MB_OK);
	}
}

string tostr(int i)
{
	char b[5];
	ZeroMemory(b,sizeof(b));

	itoa(i,b,10);
	return string(b);

}

#define A(x) MessageBox(0,x,x,MB_OK);
VOID ModelExp::writeMesh()
{
	try{
		Mtl* nodeMtl = pMesh->GetMtl();


		int vx1, vx2, vx3;
		Matrix3 tm = pMesh->GetObjTMAfterWSM(EXPTIME);
		if (TMNegParity(tm)) {vx1 = 2;vx2 = 1;vx3 = 0;}else {vx1 = 0;vx2 = 1;vx3 = 2;}

		BOOL needDel;
		TriObject* tri = GetTriObjectFromNode(pMesh, EXPTIME);
		if (!tri)
			throw Exception("Could not get triangle object from mesh node");

		Mesh* mesh = &tri->GetMesh();
		if (!mesh)
			throw Exception("Could not get mesh object from trinalge object");



		//This lets us know if we should bother exporting any data associated with
		//the specifeid mat ID
		int numVerts = mesh->getNumVerts();
		int numFaces = mesh->getNumFaces();

		numVerts = numFaces * 3;

		//**----------------
		//Bomb out
		if(numFaces==0 || numVerts == 0)
			throw Exception("No faces or vertices of object"); 



#ifdef READABLE
		fprintf(pFilePtr, "MODEL");
		fprintf(pFilePtr, "Number of Frames: %i\n\n", frames);
		fprintf(pFilePtr, "Number of Faces: %i\n\n", numFaces);
		fprintf(pFilePtr, "Number of Vertices: %i\n\n", numVerts);
		fprintf(pFilePtr, "Interval: s = %i e = %i\n\n", g_pIp->GetAnimRange().Start(), g_pIp->GetAnimRange().End());

#else
		fwrite(&numFaces, sizeof(int),1,pFilePtr);
		fwrite(&numVerts, sizeof(int),1,pFilePtr);



#endif
		face *pFaces = new face [numFaces * frames];
		int ind = 0;

		int tpf = GetTicksPerFrame();

		for(int f = 0; f < frames; f++)
		{
			g_pIp->SetTime(f*tpf);

			tm = pMesh->GetObjTMAfterWSM(f*tpf);
			if (TMNegParity(tm)) {vx1 = 2;vx2 = 1;vx3 = 0;}else {vx1 = 0;vx2 = 1;vx3 = 2;}


			
			TriObject* pTriObject = GetTriObjectFromNode(pMesh, f*tpf);
			Mesh *meshF = &pTriObject->GetMesh();

			meshF->buildNormals();
			Point3* vNormals = ComputeVertexNormals(meshF);

#ifdef READABLE
			fprintf(pFilePtr,"\nFrame %i start:",f);
#endif

			for (int i=0; i<numFaces; i++) {

				//1 == channel 1
				//i == face vert idx
				TVFace tvFace = meshF->mapFaces(1)[i];

				UVVert tcA = meshF->mapVerts(1)[(int)tvFace.t[vx1]];
				UVVert tcB = meshF->mapVerts(1)[(int)tvFace.t[vx2]];
				UVVert tcC = meshF->mapVerts(1)[(int)tvFace.t[vx3]];

				UVVert tc_lmA = UVVert();
				UVVert tc_lmB = UVVert();
				UVVert tc_lmC = UVVert();

	
			if(meshF->getNumMaps() > 2)
			{
				if(meshF->mapFaces(2) != 0)
				{
//					MessageBox(0,">","?",0);

			TVFace tv_lmFace = meshF->mapFaces(2)[i];
			tc_lmA = meshF->mapVerts(2)[(int)tv_lmFace.t[vx1]];
			tc_lmB = meshF->mapVerts(2)[(int)tv_lmFace.t[vx2]];
			tc_lmC = meshF->mapVerts(2)[(int)tv_lmFace.t[vx3]];


				}
			}
//A("3.6")

				Point3 vA = tm*meshF->verts[meshF->faces[i].v[vx1]];// * tm;
				Point3 vB = tm*meshF->verts[meshF->faces[i].v[vx2]];// * tm;
				Point3 vC = tm*meshF->verts[meshF->faces[i].v[vx3]];// * tm;
//A("3.7")

				Point3 nA = vNormals[mesh->faces[i].v[vx1]];//meshF->getFaceNormal(i);//
				Point3 nB = vNormals[mesh->faces[i].v[vx2]];//meshF->getFaceNormal(i);//
				Point3 nC = vNormals[mesh->faces[i].v[vx3]];//meshF->getFaceNormal(i);//
				nA.Normalize();
				nB.Normalize();
				nC.Normalize();
//A("3.8")
#ifdef READABLE
				fprintf(pFilePtr,"\nf%i: (%f %f %f) (%f %f)",i,vA.x,vA.y,vA.z,tcA.x,tcA.y);
				fprintf(pFilePtr,"\nf%i: (%f %f %f) (%f %f)",i,vB.x,vB.y,vB.z,tcB.x,tcB.y);
				fprintf(pFilePtr,"\nf%i: (%f %f %f) (%f %f)\n*",i,vC.x,vC.y,vC.z,tcC.x,tcC.y);
#else 

				vertice v;
				ZeroMemory(&v,sizeof(vertice));
				v.pos[0] = vA.x;	v.pos[1] = vA.y;	v.pos[2] = vA.z;
				v.tc[0] = tcA.x;	v.tc[1] = tcA.y;
				v.tc_lm[0] = tc_lmA.x;	
				v.tc_lm[1] = tc_lmA.y;	
				
				v.n[0] = nA.x;
				v.n[1] = nA.y;
				v.n[2] = nA.z;
				fwrite(&v, sizeof(vertice), 1, pFilePtr);	

				ZeroMemory(&v,sizeof(vertice));
				v.pos[0] = vB.x;	v.pos[1] = vB.y;	v.pos[2] = vB.z;
				v.tc[0] = tcB.x;	v.tc[1] = tcB.y;
				v.tc_lm[0] = tc_lmB.x;	
				v.tc_lm[1] = tc_lmB.y;	

				v.n[0] = nB.x;
				v.n[1] = nB.y;
				v.n[2] = nB.z;
				fwrite(&v, sizeof(vertice), 1, pFilePtr);	

				ZeroMemory(&v,sizeof(vertice));
				v.pos[0] = vC.x;	v.pos[1] = vC.y;	v.pos[2] = vC.z;
				v.tc[0] = tcC.x;	v.tc[1] = tcC.y;
				v.tc_lm[0] = tc_lmC.x;	
				v.tc_lm[1] = tc_lmC.y;	

				v.n[0] = nC.x;
				v.n[1] = nC.y;
				v.n[2] = nC.z;
				fwrite(&v, sizeof(vertice), 1, pFilePtr);	

				pFaces[i].indice[0] = ind;
				pFaces[i].indice[1] = ind + 1;
				pFaces[i].indice[2] = ind + 2;
				pFaces[i].n[0] = 
					pFaces[i].n[1] = 
					pFaces[i].n[2] = 0;
				ind+=3;
				//A("3.9")

#endif
			}
		}
//A("4")

#ifdef READABLE
		fprintf(pFilePtr, "\n----------");
#endif


	}catch(ModelExp::Exception e)
	{
		MessageBox(0,e.msg.c_str(),"Error",MB_OK);
	}
}

TriObject* ModelExp::GetTriObjectFromNode(INode *node, TimeValue t)
{

	Object *obj = node->EvalWorldState(t).obj;
	if (obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) { 
		TriObject *tri = (TriObject *) obj->ConvertToType(t, 
			Class_ID(TRIOBJ_CLASS_ID, 0));
		// Note that the TriObject should only be deleted
		// if the pointer to it is not equal to the object
		// pointer that called ConvertToType()

		return tri;
	}
	else {
		return NULL;
	}
}

BOOL ModelExp::TMNegParity(Matrix3 &m)
{
	return (DotProd(CrossProd(m.GetRow(0),m.GetRow(1)),m.GetRow(2))<0.0)?1:0;
}
